# -*- coding: utf-8 -*-

import datetime
from openerp import models,SUPERUSER_ID
from openerp.osv import osv, fields, orm
import logging

ISODATETIMEFORMAT = "%Y-%m-%d %H:%M:%S"

_logger = logging.getLogger(__name__)

# 扩展订单字段, 发票类型：不开发票、普票、增票; 预计发货时间; 运费类型：包运费、垫付、到付;
class sale_order_ext(models.Model):
    _name = 'sale.order'
    _inherit = 'sale.order'

    # 是否打印字段状态显示转换函数
    def display_print_state(self, cr, uid, ids, name, arg, context=None):
        res = {}
        for order in self.browse(cr, uid, ids, context=context):
            if order.print_state:
                res[order.id] = u'已打印√'
            else:
                res[order.id] = ''
        return res

    _columns = {
        'tax_invoice_type': fields.selection([('no_invoice', u'不开发票'),
                                              ('normal_invoice', u'普通发票'),
                                              ('specialty_invoice', u'专业增值税发票')], u'税务发票类型', required=True),
        'freight_type': fields.selection([
                                             ('freight_paid', u'垫付'),
                                             ('freight_free', u'包运费'),
                                             ('freight_collect', u'到付'),
                                             ], u"运费类型", required=True),
        'estimated_delivery_time': fields.date(string=u"预计发货时间", copy=False),
        'print_state': fields.boolean(string=u"订单打印状态", copy=False),
        'print_state_display': fields.function(display_print_state, string=u'打印状态', type='text'),
        'state': fields.selection([
                                      ('draft', 'Draft Quotation'),
                                      ('sent', u'待审批'),
                                      ('cancel', 'Cancelled'),
                                      ('waiting_date', 'Waiting Schedule'),
                                      ('progress', u'正在处理'),
                                      ('manual', 'Sale to Invoice'),
                                      ('shipping_except', 'Shipping Exception'),
                                      ('invoice_except', 'Invoice Exception'),
                                      ('done', 'Done'),
                                      ], 'Status', readonly=True, copy=False, help="Gives the status of the quotation or sales order.\
              \nThe exception status is automatically set when a cancel operation occurs \
              in the invoice validation (Invoice Exception) or in the picking list process (Shipping Exception).\nThe 'Waiting Schedule' status is set when the invoice is confirmed\
               but waiting for the scheduler to run on the order date.", select=True),

        #增加快照字段，纪录确认(审批)订单时的信用额度和应收款项(暂时没用，准备去除)
        #'snapshot_credit_limit': fields.float(string=u'信用额度快照'),
        #应收快照可以用在订单可支付检查中
        'snapshot_receivable': fields.float(string=u'应收快照', copy=False),
        #
        'customer_pick_up': fields.boolean(string=u'客户自提'),
        'department_id': fields.many2one('hr.department', u'销售部门'),
        #订单是否被客户余额支付
        'is_enough_payment': fields.boolean(string=u'余额够支付', copy=False),
        #足够支付的日期
        'enough_payment_date': fields.date(string=u'余额足够支付时间', copy=False),
        #运费(冗余到订单上便于统计)
        'freight': fields.float(string=u'运费', copy=False),
        #是否退货单
        'is_return': fields.boolean(string=u'是否退货单', copy=False),
        'date_order': fields.datetime(u'确认时间', readonly=True, required=False,copy=False),
    }
    _defaults = {
        #生成发票默认方式为发货前(prepaid预付)
        #'order_policy': 'picking',
        #预计发货时间默认是3天后
        'estimated_delivery_time': datetime.datetime.today() + datetime.timedelta(days=3),
        'tax_invoice_type': 'no_invoice',
        'warehouse_id': None,
        'date_order': False,
        #'freight_type': 'freight_free',
    }

    #检查优惠金额是否超过设定值(在sale.order.base流程中新增了一个迁移)
    def check_rebate(self, cr, uid, ids, limit=-10, context=None):
        for order in self.browse(cr, uid, ids, context=context):
            total_rebate = 0
            for line in order.order_line:
                if line.price_subtotal < 0:
                    total_rebate += line.price_subtotal
            if total_rebate < limit:
                return False
        #如果检查信用余额不足则抛出例外
        self.check_balance(cr, uid, ids, context)
        return True

    #检查客户信余额是否大于订单金额
    def check_balance(self, cr, uid, ids, context=None):
        assert len(ids) == 1, 'This option should only be used for a single id at a time.'
        order = self.browse(cr, uid, ids, context=context)[0]
        partner = order.partner_id
        #应收款合计(负数表示欠客户的或者客户可用于支付订单的额度)
        credit = partner.credit
        #设定的信用额度
        credit_limit = partner.credit_limit
        #如果不是公司且有父级公司，则取父级客户公司的应收款和信用额度
        if not partner.is_company and partner.parent_id:
            credit = partner.parent_id.credit
            credit_limit = partner.parent_id.credit_limit
        credit_or_prepaid = u'应收:%s' % credit
        if credit < 0:
            credit_or_prepaid = u'预付:%s' % credit

        if partner.balance < order.amount_total:
            raise osv.except_osv(u'警告!', u'客户余额或授信额度不足(信用:%s, %s), 信用余额:%s, 订单金额:%s'
                                 % (credit_limit, credit_or_prepaid, partner.balance, order.amount_total))
        return credit


    def write(self, cr, uid, ids, vals, context=None):
        if vals.get('state') == 'progress':
            order = self.browse(cr, uid, ids, context=context)[0]
            #写入确认时间
            if not order.date_order:
                vals.setdefault('date_order', datetime.datetime.now().strftime(ISODATETIMEFORMAT))
                #order.write({'date_order': datetime.datetime.now().strftime(ISODATETIMEFORMAT)})
        res = super(sale_order_ext, self).write(cr, uid, ids, vals, context=context)
        return True


    #重载提交审核（原发送报价单）按钮
    def action_quotation_send(self, cr, uid, ids, context=None):
        self.signal_workflow(cr, 1, ids, 'quotation_sent')
        return True


    #重载订单确认方法，检查信用余额是否足够
    def action_button_confirm(self, cr, uid, ids, context=None):
        assert len(ids) == 1, 'This option should only be used for a single id at a time.'
        #如果检查信用余额不足则抛出例外
        self.check_balance(cr, 1, ids, context)
        self.signal_workflow(cr, 1, ids, 'order_confirm')
        return True

    #继承订单销售员id改变事件，更改部门ID冗余字段
    def onchange_user_id(self, cr, uid, ids, user_id, context=None):
        vals = {}
        employee_ids = self.pool['hr.employee'].search(cr, 1, [('user_id', '=', user_id)])
        if employee_ids:
            employee_id = employee_ids[0]
            employee = self.pool['hr.employee'].browse(cr, 1, employee_id, context=context)
            if employee.department_id:
                vals['department_id'] = employee.department_id.id
            else:
                vals['department_id'] = ''
        else:
            vals['department_id'] = ''
        return {'value': vals}

    #继承订单客户id改变事件，初始化部门ID冗余字段
    def onchange_partner_id(self, cr, uid, ids, part=False, context=None):
        partner = self.pool.get('res.partner').browse(cr, uid, part, context=context)
        #如果客户是联系人，强制设为父级公司客户
        if not partner.is_company and partner.parent_id:
            part = partner.parent_id.id
        vals = super(sale_order_ext, self).onchange_partner_id(cr, uid, ids, part, context=context)
        vals['value']['partner_id'] = part
        if vals['value'].has_key('user_id'):
            user_id = vals['value']['user_id']
            user_id_onchange = self.onchange_user_id(cr, uid, ids, user_id,  context=None)
            vals.update(user_id_onchange['value'])
        return vals

    #处理所有未足够支付的订单
    def process_all_not_enough_payment_orders(self, cr, uid, context=None):
        obj_partner = self.pool['res.partner']
        partner_ids = obj_partner.search(cr, uid, [('is_company', '=', True), ('active', '=', True)])
        today = fields.date.context_today(self, cr, uid, context=context)
        yesterday = datetime.datetime.strptime(today, '%Y-%m-%d') - datetime.timedelta(days=1)
        yesterday = datetime.datetime.strftime(yesterday, '%Y-%m-%d')
        #更新当天确认的订单运费
        update_sql = """
            update sale_order so
                set freight=(select sum(sol.product_uom_qty*sol.price_unit)
                         from sale_order_line sol
                         where sol.order_id=so.id and sol.product_id=1)
            where so.state not in ('draft', 'sent', 'cancel') and date(date_confirm)='%s'
        """ % (yesterday)
        cr.execute(update_sql)

        for partner_id in partner_ids:
            partner = obj_partner.browse(cr, uid, partner_id)
            #检查是否是联系人，取得所有联系人加上父级id
            parent_id = partner_id
            if partner.parent_id:
                parent_id = partner.parent_id.id
            #child_partner_ids = obj_partner.search(cr, uid, [('parent_id', '=', parent_id)])
            child_partner_ids = [c.id for c in partner.child_ids]
            child_partner_ids.append(parent_id)

            #如果客户有预付款或者未欠款，客户的所有的订单都可以支付
            if partner.credit <= 0:
                order_ids = self.search(cr, uid, [('partner_id', 'in', child_partner_ids), ('state', 'not in', ['draft', 'sent', 'cancel', 'invoice_except']), '|', ('is_enough_payment', '=', False), ('is_enough_payment', '=', None)], order="date_confirm")
                for order_id in order_ids:
                    order = self.browse(cr, uid, order_id, context=context)
                    order.write({'snapshot_receivable': partner.credit, 'is_enough_payment': True,
                                 'enough_payment_date': yesterday})
            #如果客户有欠款，不足以支付，则从最新的订单开始累加，找到不足以支付的那个分割点，早于这个订单的所有订单都可以被支付
            else:
                #取得这个客户的所有未足够支付订单
                order_ids = self.search(cr, uid, [('partner_id', 'in', child_partner_ids), ('state', 'not in', ['draft', 'sent', 'cancel', 'invoice_except']), '|', ('is_enough_payment', '=', False), ('is_enough_payment', '=', None)], order="date_confirm desc")
                no_enough_payment_order_sum = 0
                order_break_point = False
                for order_id in order_ids:
                    order = self.browse(cr, uid, order_id, context=context)
                    #如果找到分割点，则之后的订单都设为可支付
                    if order_break_point:
                        order.write({'snapshot_receivable': partner.credit, 'is_enough_payment': True,
                                     'enough_payment_date': yesterday})
                    #通过累加正的订单金额找订单分割点
                    #假设3个订单未支付，订单金额从早到晚排列分别为100、200、300，客户的应收款310，则找到300+200大于等于310为止，之后的100订单是可支付的
                    else:
                        #如果订单金额大于0才计算总的欠款额
                        if order.amount_total > 0:
                            no_enough_payment_order_sum += order.amount_total
                            if no_enough_payment_order_sum >= partner.credit:
                                order_break_point = True
                        #如果小于等于0则设为已可支付
                        else:
                            order.write({'snapshot_receivable': partner.credit, 'is_enough_payment': True,
                                     'enough_payment_date': yesterday})

    #根据订单建立入库单
    def create_stock_move_in(self, cr, uid, ids, context=None):
        if isinstance(ids, (int, long)):
            ids = [ids]

        procurement_group_obj = self.pool.get("procurement.group")
        pick_obj = self.pool.get("stock.picking")
        incoming_picking_type_id = self.pool.get('stock.picking.type').search(cr, uid, [('code', '=', 'incoming')], context=context)[0]

        #订单遍历
        for order in self.browse(cr, uid, ids, context=context):
            ###建立需求组
            # values = {
            #     'name': order.name,
            #     'move_type': order.picking_policy or 'direct',
            #     'partner_id': order.partner_id.id or False,
            # }
            values = self._prepare_procurement_group(cr, uid, order, context=context)
            if not order.procurement_group_id:
                group_id = procurement_group_obj.create(cr, uid, values, context=context)
                order.write({'procurement_group_id': group_id}, context=context)
            else:
                group_id = order.procurement_group_id.id

            #### 建立stock.picking
            values = {
                'origin': order.name,
                'company_id': order.company_id and order.company_id.id or False,
                'move_type': order.picking_policy or 'direct',
                'partner_id': order.partner_id.id or False,
                'picking_type_id': incoming_picking_type_id,
                #'group_id': group_id, 自动会从stock.move中取得并存储
           }
            pick_id = pick_obj.create(cr, uid, values, context=context)
            #### 建立stock.move
            #picking = pick_obj.browse(cr, uid, pick_id, context=context)
            #picking = pick_obj.browse(cr, uid, pick_id, context=context)
            stock_move_obj = self.pool.get('stock.move')
            for line in order.order_line:
                if line.product_id.type in ['service']:
                    continue
                vals = {
                    'company_id': order.company_id and order.company_id.id or False,
                    'group_id': group_id,
                    'picking_id': pick_id,
                    'picking_type_id': incoming_picking_type_id,
                    'location_id': order.partner_shipping_id.property_stock_supplier.id,
                    'location_dest_id': order.company_id.partner_id.property_stock_customer.id,
                    'name': u'退货:' + (line.name or ''),
                    'product_id': line.product_id.id,
                    'product_uom': line.product_uom.id,
                    'product_uom_qty': abs(line.product_uom_qty),
                    'date': fields.datetime.now(),
                    'state': 'confirmed',
                    'origin': order.name,
                 }
                stock_move_obj.create(cr, uid, vals, context=context)

    #重载建立出货单函数，实现是否退货单的判断和建立退货入库单
    def action_ship_create(self, cr, uid, ids, context=None):
        assert len(ids) == 1, u'只能处理一个订单的建立出货单或退货入库单'
        order = self.browse(cr, uid, ids, context=context)[0]
        #写入确认时间
        if not order.date_order:
            order.write({'date_order': datetime.datetime.now().strftime(ISODATETIMEFORMAT)})

        if order.amount_total < 0:
            order.write({'is_return': True}, context=context)
            self.create_stock_move_in(cr, uid, ids, context=context)
        else:
            #super(sale_order_ext, self).action_ship_create(cr, uid, ids, context=context)
            procurement_obj = self.pool.get('procurement.order')
            sale_line_obj = self.pool.get('sale.order.line')
            for order in self.browse(cr, uid, ids, context=context):
                proc_ids = []
                vals = self._prepare_procurement_group(cr, uid, order, context=context)
                if order.procurement_group_id:
                    group_id = order.procurement_group_id.id
                #if not order.procurement_group_id:
                else:
                    group_id = self.pool.get("procurement.group").create(cr, uid, vals, context=context)
                    order.write({'procurement_group_id': group_id})

                for line in order.order_line:
                    #Try to fix exception procurement (possible when after a shipping exception the user choose to recreate)
                    if line.procurement_ids:
                        #first check them to see if they are in exception or not (one of the related moves is cancelled)
                        procurement_obj.check(cr, uid, [x.id for x in line.procurement_ids if x.state not in ['cancel', 'done']])
                        line.refresh()
                        #run again procurement that are in exception in order to trigger another move
                        proc_ids += [x.id for x in line.procurement_ids if x.state in ('exception', 'cancel')]
                        procurement_obj.reset_to_confirmed(cr, uid, proc_ids, context=context)
                    elif sale_line_obj.need_procurement(cr, uid, [line.id], context=context):
                        if (line.state == 'done') or not line.product_id:
                            continue
                        vals = self._prepare_order_line_procurement(cr, uid, order, line, group_id=group_id, context=context)
                        proc_id = procurement_obj.create(cr, uid, vals, context=context)
                        proc_ids.append(proc_id)
                #Confirm procurement order such that rules will be applied on it
                #note that the workflow normally ensure proc_ids isn't an empty list
                procurement_obj.run(cr, uid, proc_ids, context=context)

                #if shipping was in exception and the user choose to recreate the delivery order, write the new status of SO
                if order.state == 'shipping_except':
                    val = {'state': 'progress', 'shipped': False}

                    if (order.order_policy == 'manual'):
                        for line in order.order_line:
                            if (not line.invoiced) and (line.state not in ('cancel', 'draft')):
                                val['state'] = 'manual'
                                break
                    order.write(val)
        return True

    #重载准备发票函数，负数订单（退货单）需要生成红字发票
    def _prepare_invoice(self, cr, uid, order, lines, context=None):
        invoice_vals = super(sale_order_ext, self)._prepare_invoice(cr, uid, order, lines, context=context)
        if order.amount_total < 0:
            journal_ids = self.pool.get('account.journal').search(cr, uid,
            [('type', '=', 'sale_refund'), ('company_id', '=', order.company_id.id)],
            limit=1)
            invoice_vals.update({
                'type': 'out_refund',
                'journal_id': journal_ids[0]
            })
            obj_invoice_line = self.pool.get('account.invoice.line')
            for inv_line_id in lines:
                inv_line = obj_invoice_line.browse(cr, uid, inv_line_id, context=context)
                inv_line.write({'quantity': 0 - inv_line.quantity,
                                'price_unit': inv_line.price_unit,
                                'price_subtotal': 0 - inv_line.price_subtotal})
        return invoice_vals

    def _product_margin(self, cr, uid, ids, field_name, arg, context=None):
        result = {}
        for sale in self.browse(cr, uid, ids, context=context):
            result[sale.id] = 0.0
            for line in sale.order_line:
                result[sale.id] += line.margin or 0.0
            #如果是正常订单，且毛利小于0，则强行改成0
            if sale.amount_total > 0 and result[sale.id] < 0:
                result[sale.id] = 0
            #如果是退货单，且毛利大于0，则强行改成0
            if sale.amount_total < 0 and result[sale.id] > 0:
                result[sale.id] = 0
        return result